package com.echo.im.engin.netty.ws;


import com.echo.im.engin.netty.IMServer;
import com.echo.im.engin.netty.handler.DisconnectHandler;
import com.echo.im.engin.netty.handler.HeartbeatHandler;
import com.echo.im.engin.netty.handler.IMChannelHandler;
import com.echo.im.engin.netty.handler.LoginHandler;
import com.echo.im.engin.netty.handler.PacketCodecHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 在 Netty 中，router 的顺序是非常重要的，因为它决定了消息在 pipeline 中的处理顺序。在你的代码中，你正确地将编解码器放在了业务逻辑处理器之前。这样可以确保在消息到达业务逻辑处理器之前已经被正确地编码或解码。以下是一些建议：
 *
 * IdleStateHandler 应当放在 pipeline 的最前面，这样可以确保任何潜在的超时都能尽早被捕捉并处理。
 * HttpServerCodec、HttpObjectAggregator 和 ChunkedWriteHandler 是处理 HTTP 请求的标准组合，它们的顺序是合理的。
 * WebSocketServerProtocolHandler 应放在处理 HTTP 相关 router 之后，以确保 WebSocket 握手可以正确处理。
 * 业务逻辑相关的 router（如 MessageProtocolEncoder、MessageProtocolDecoder、HeartbeatHandler 等）应该在 WebSocket 握手完成后处理 WebSocket 消息。
 *
 * @author echo
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketServer implements IMServer {

    private volatile boolean ready = false;


    @Autowired(required = false)
    private EventLoopGroup bossGroup;
    @Autowired(required = false)
    private EventLoopGroup workGroup;

    @Value("${websocket.port}")
    private int port;

    @Override
    public boolean isReady() {
        return ready;
    }

    @Override
    public void start() {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bossGroup = new NioEventLoopGroup(1);
        workGroup = new NioEventLoopGroup();

        bootstrap.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128) // 更适合高并发场景
                // 表示连接保活，相当于心跳机制，默认为7200s
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<Channel>() {
                    // 添加处理的Handler，通常包括消息编解码、业务处理，也可以是日志、权限、过滤等
                    @Override
                    protected void initChannel(Channel ch) {
                        // 获取职责链
                        ChannelPipeline pipeline = ch.pipeline();
                        //TODO 调大一点测试
                        pipeline.addLast(new IdleStateHandler(600, 600, 600, TimeUnit.SECONDS));
                        pipeline.addLast("http-codec", new HttpServerCodec());
                        pipeline.addLast("aggregator", new HttpObjectAggregator(65535));
                        pipeline.addLast("http-chunked", new ChunkedWriteHandler());
                        pipeline.addLast(new WebSocketServerProtocolHandler("/im"));
                        pipeline.addLast("codec", new PacketCodecHandler());
                        pipeline.addLast("login", new LoginHandler());
                        pipeline.addLast("heartbeat", new HeartbeatHandler());
                        pipeline.addLast("router", new IMChannelHandler());
                        pipeline.addLast("disconnect", new DisconnectHandler());
                    }
                });
        // 绑定端口，启动select线程，轮询监听channel事件，监听到事件之后就会交给从线程池处理
        try {

            //这是 ServerBootstrap 类的一个方法，用于绑定服务器到指定的端口。它准备服务器以接受进入的网络连接
            ChannelFuture channelFuture = bootstrap.bind(port);

            // 使用 sync() 是为了确保在继续执行任何后续代码之前，端口绑定操作已经完成。
            ChannelFuture sync = channelFuture.sync();

            /**
             * 在 Netty 的架构中，Channel 代表一个网络套接字的开放连接或绑定，而不仅仅是一个已经建立的客户端到服务器的连接。这个概念初听起来可能有些混淆，因为我们通常认为一个“通道”应该是连接两点的东西，比如一个电话线路。然而，在 Netty 中，Channel 更广泛地代表了任何形式的能够进行网络通信的载体。
             *
             * 为什么在服务器启动时需要一个 Channel？
             * 在你提供的代码片段中，服务器启动时创建的 Channel 实际上是用于监听进入的连接请求的。这个 Channel 并不代表一个与特定客户端的活动连接，而是服务器在一个网络端口上的监听状态。其主要作用和重要性可以从以下几个方面理解：
             *      监听端口：这个 Channel 是一个 NioServerSocketChannel，它在指定端口上监听进入的连接请求。它的作用相当于一个门卫，决定哪些客户端可以建立连接。
             *      事件分发：这个监听 Channel 负责接收到的连接请求，并将这些请求分发给服务器的工作线程（工作线程组）。这是通过 Netty 的事件循环（EventLoop）机制实现的，其中 Channel 负责监视和分发网络事件（如连接请求、数据到达等）。
             *      连接的接收器：一旦有客户端尝试连接到服务器，这个 Channel 就会处理 TCP 的三次握手过程。握手成功后，会创建一个新的 Channel 实例专门用于与该客户端的后续通信。
             *      配置传递：在服务器启动时创建的 Channel 还用于配置网络参数（如 TCP 的 backlog 队列大小，是否启用 keepalive 等），这些设置决定了服务器处理连接的方式。
             * 具体流程
             *      当你调用 bootstrap.bind(port) 时，实际上你在网络上的那个端口创建了一个监听 Channel。这个操作让你的服务器能够开始接受来自客户端的连接请求。一旦 sync() 被调用，主线程将阻塞直到端口绑定完成，确保没有错误发生（例如端口已被占用）。
             *      因此，即使在客户端实际连接之前，服务器需要这个 Channel 来确立一个监听点，准备接收和处理即将到来的连接请求。这个 Channel 的存在是使服务器能够开始其网络服务的基础。
             */
            Channel channel = sync.channel();
            // 就绪标志
            this.ready = true;
            log.info("websocket engin 初始化完成,端口：{}", port);
            // 等待服务端口关闭
        } catch (InterruptedException e) {
            log.info("websocket engin 初始化异常", e);
        }
    }

    /**
     * 在使用 Netty 时，正确管理资源非常关键
     */
    @Override
    public void stop() {
        if (bossGroup != null && !bossGroup.isShuttingDown() && !bossGroup.isShutdown()) {
            bossGroup.shutdownGracefully();
        }
        if (workGroup != null && !workGroup.isShuttingDown() && !workGroup.isShutdown()) {
            workGroup.shutdownGracefully();
        }
        this.ready = false;
        log.info("websocket engin 停止");
    }
}
